Skip to main content

Chapter 15 - Input Variables

Parameterizing your code to make it more repeatable

Types


Primitive:

  • string
  • number
  • bool

Complex:

  • list
  • map

Structural:

  • object
  • tuple

Basics


Variables typically go into a variables.tf file.

variable "variable_name" {
description = "Description of the Variable"
default = "defaultvalue"
}

In your variables.tf:

variable "resource_group_name" {
description = "Resource Group Name"
type = string
default = "myrg"
}
variable "resource_group_location" {
description = "Resource Group Location"
type = string
default = "East US"
}

In your RG.tf:

resource "azurerm_resource_group" "myrg" {
name = "${var.resource_group_name}_RG" # to append
location = var.resource_group_location # to call the variable
}

The theory here is that you can programmatically assign entirely different names of resources just based on editing a variables.tf file. You can take this one step further in the pipelines by sending the same code through the pipelines with different variables.

Assign when prompted


This will prompt you after you run the terraform plan/apply. You have a good chance of making a mistake if you run a tf apply and type in something like "sunbet" :) In your variables.tf:

variable "resource_group_name" {
description = "Resource Group Name"
type = string
# remove the default
}
variable "resource_group_location" {
description = "Resource Group Location"
type = string
# remove the default
}

Override with default variables via the CLI


terraform plan -var="resource_group_name=mysubbedname_rg"

Terraform.tfplan


Intro to the terraform plan -out terraform.tfplan and the terrform apply terraform.tfplan You ideally want to add this to the .gitignore so you don't commit that file.

Environment variables


You can set environment variables - subscription ID, etc and then refer to them via the terraform. You would need to unset the environment variables if running your terraform on the same machine

export TF_VAR_resoure_group_name=rgenv
export TF_VAR_resoure_group_location=westus2
export TF_VAR_virtual_network_name=vnetenv
export TF_VAR_subnet_name=subnetenv

echo $variablename

unset TF_VAR_resoure_group_name
unset TF_VAR_resoure_group_location
unset TF_VAR_virtual_network_name
unset TF_VAR_subnet_name

Tfvars file


You can include the variables in a terraform tfvars file inside of your code.

business_unit          = "tax"
environment = "dev"
resoure_group_name = "rg"
resoure_group_location = "centralus"
virtual_network_name = "vnet"
subnet_name = "subnet"

Running a terraform plan here will flatten out all of the files and use the tfvars.

-var-file="file.tfvars"


You can separate out:

  • dev.tfvars
  • qa.tfvars
  • prod.tfvars

and then run `terraform plan -var-file="qa.tfvars"

You can then run this with the terraform.tfvars file together to delineate between dev and qa.

There is an issue with this that we will cover later, using workspaces. If you build the dev resources, then run the qa resources, the dev resources will be destroyed in favor of the qa resources. You can create a dev, qa workspace to run each of these pieces of code in.

auto.tfvars


Adding auto to the tfvars makes them load by default alongside the terraform.tfvars.

Constructors like List and Map


Types and type constraints. List - [] Map -

You can use the list [] like this:

variable "virtual_network_address_space" {
description = "Virtual Network Address Space"
type = list(string)
default = ["10.0.0.0/16", "10.1.0.0/16", "10.2.0.0/16"]
}

resource "azurerm_virtual_network" "myvnet" {
name = "${var.business_unit}-${var.environment}-${var.virtual_network_name}"
#address_space = ["10.0.0.0/16"]
address_space = var.virtual_network_address_space # for all 3
# or
#address_space = [var.virtual_network_address_space[0]] #for one
location = azurerm_resource_group.myrg.location
resource_group_name = azurerm_resource_group.myrg.name
}

Maps


Maps are a group of key value pairs (objects). Default is a map below.

variable "public_ip_sku" {
description = "Azure Public IP Address SKU"
type = map(string)
default = {
"eastus" = "Basic",
"eastus2" = "Standard"
}
}

# Tags
variable "common_tags" {
description = "Common Tags for Azure Resources"
type = map(string)
default = {
"Tool" = "Terraform",
"Cloud" = "Azure"
}
}

# Resource
resource "azurerm_public_ip" "mypublicip" {
name = "mypublicip-1"
resource_group_name = azurerm_resource_group.myrg.name
location = azurerm_resource_group.myrg.location
allocation_method = "Static"
domain_name_label = "app1-vm-${random_string.myrandom.id}"
#sku = var.public_ip_sku["eastus"]
tags = var.common_tags
}

lookup


https://developer.hashicorp.com/terraform/language/functions/lookup

Element = List lookup = map :)

lookup(map, key, default) = you use the key to look up something in the map, if it's not found, return the default.

If you are using keys that start with numbers, you need to use : instead of = From the previous example, sku can use lookup like so.

resource "azurerm_public_ip" "mypublicip" {
name = "mypublicip-1"
resource_group_name = azurerm_resource_group.myrg.name
location = azurerm_resource_group.myrg.location
allocation_method = "Static"
domain_name_label = "app1-vm-${random_string.myrandom.id}"
sku = lookup(var.public_ip_sku, var.resoure_group_location, "Basic")
tags = var.common_tags
}

# Functions
---
- length() - length of the string, number of items in a map or array
- substr() - can select a portion of the string (string, offset, number of characters)
- contains() - check to see if the input contains a value
- lower() - to lower
- upper() - TO UPPER
- regex()
- can()

# Custom Validation
---
```hcl
variable "resource_group_location" {
description = "Resource Group Location"
type = string
default = "eastus"
validation {
condition = var.resource_group_location == "eastus" || var.resource_group_location =="eastus2"
error_message = "We only allow Resources to be created in eastus or eastus2 locations."
}
}

You can also use contains() here as well :)

Regex and Can functions


condition = can(regex("india$", var.resoure_group_location))

Sensitive Variables


Exposed in plain text in your state file.

  1. Create a secrets.tfvars
  2. Put your secret variables in here
  3. Don't commit this file. run `terraform plan -var-file="secrets.tfvars" in our environment this might be our beconf.tfvars files.

Boolean


type = bool
default = true

Number


type = number
default = 18

Structural Types


https://developer.hashicorp.com/terraform/language/expressions/type-constraints

Objects


This is like defining a map of strings, you can create an object with different data types. key = type

variable "os_configs" {
type = object({
location = string
size = string
instance_count = number
})
}

Tuples


A tuple is similar to an object but not in a key = value sense Instead of key = type it is simply type.

["a", 15, true]

In the Terraform Code:

# DB Variables
db_name = "mydb12424"
db_storage_mb = 5120
db_auto_grow_enabled = true

# This is an example of an object
tdpolicy = {
enabled = true
retention_days = 10
email_account_admins = true
email_addresses = [ "solo@gmail.com", "liz@gmail.com" ]
}
# redefined as a tuple:
tdpolicy = [true, 10, true, [ "solo@gmail.com", "liz@gmail.com" ]]

Defined as a variable:

# 12. Azure MySQL DB Threat Detection Policy (Variable Type: tuple)
variable "tdpolicy" {
description = "Azure MySQL DB Threat Detection Policy"
type = tuple([bool, number, bool, list(string) ])
}

and in the resource:

resource "azurerm_mysql_server" "mysqlserver" {
name = "${var.business_unit}-${var.environment}-${var.db_name}"
...
...
threat_detection_policy {
enabled = var.tdpolicy[0]
retention_days = var.tdpolicy[1]
email_account_admins = var.tdpolicy[2]
email_addresses = var.tdpolicy[3]
}

Set


A group of unique values.


variable "environment" {
description = "Environment Name"
type = set(string)
default = ["dev1", "qa1", "staging1", "prod1"]
}

Then, you can create resources for each of those in the set:

# Resource-1: Azure Resource Group
resource "azurerm_resource_group" "myrg" {
for_each = var.environment
name = "${var.business_unit}-${each.key}-${var.resoure_group_name}"
location = var.resoure_group_location
}

# Resource 2: VNet
resource "azurerm_virtual_network" "myvnet" {
for_each = var.environment
name = "${var.business_unit}-${each.key}-${var.virtual_network_name}"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.myrg[each.key].location
resource_group_name = azurerm_resource_group.myrg[each.key].name
}

# Resource 3: Subnet
resource "azurerm_subnet" "mysubnet" {
for_each = var.environment
#name = "mysubnet-1"
name = "${var.business_unit}-${each.key}-${var.virtual_network_name}-mysubnet"
resource_group_name = azurerm_resource_group.myrg[each.key].name
virtual_network_name = azurerm_virtual_network.myvnet[each.key].name
address_prefixes = ["10.0.2.0/24"]
}